#include "sys_uart.h"
#include "sys_clock.h"
#include "sys_gpio.h"
#include "sys_delay.h"
#include "sys_lcd.h"
#include "sys_cache.h"
#include "sys_interrupt.h"
#include "sys_timer.h"
#include "sys_dma.h"
#include "sys_sd.h"
#include <string.h>
#include <stdio.h>
#include <sys_mmu.h>
#include "FreeRTOS.h"
#include "task.h"
#include "nes_main.h"
#include "ff.h"
#include "ch9350_input.h"

//LVGL
#include "lv_port_disp.h"
#include "lv_port_indev.h"
#include "lvgl.h"
#include "lv_conf.h"

//--------------
#include "sys_lcd_conf.h"
#include "sys_touch_conf.h"
//--------------

#include "sys_cfg.h"

#ifdef EVB_IO
#define DEBUG_UART_PORT UART0
#else
#define DEBUG_UART_PORT UART2
#endif

#define ENV_OFFSET    0x80010000

//#define CPU_FREQUENCY 600000000
#define CPU_FREQUENCY 798000000

extern void desktop_start(); // in desktop.c
extern void desktop_refresh();

/*---------------------------------------------------
FreeRTOS移植到F1C100S说明：
1.portASM.S 中 vPreemptiveTick在freertos_vectors.s的IRQ中断被调用
	程序中判断中断类型，1【是Tick中断则进入Tick,需要切换任务时则进入上下文切换,最后清中断标志】 2【当不是Tick中断时则进入其它中断】
	xTaskIncrementTick 完成系统Tick
	vTaskSwitchContext 当有多个任务时完成上下文切换
2.portASM.S 中 vPortYieldProcessor在freertos_vectors.s的SWI软中断被调用
	vPortYield 进入SWI软中断时调用
	vPortYieldProcessor 软中断处理程序
	vPortStartFirstTask 进入第一任务【恢复上下文则进入第一任务】
3.ptrt.c 中 prvSetupTimerInterrupt 初始化Tick定时器
4.FreeRTOSConfig.h 中配置各种参数，包括系统堆栈大小等
----------------------------------------------------*/
typedef struct app_env_t {
	unsigned int card_ok;
	unsigned int card_type;
	unsigned int card_uRCA;
} APP_ENV;

APP_ENV the_app_env;

ergo_sys_cfg_t g_ergo_sys_cfg;

volatile int is_emu_running;
int event_gui_done;

FATFS the_fs;
static char cur_dir[255];

void CMD_Task(void* pdata)
{
	int r;
	char str_cmds[64];

	sysprintf("CMD_Task %08X\r\n", (int)pdata);

	cur_dir[0] = 0;
	f_getcwd(cur_dir, sizeof(cur_dir));

	for (;;) {
		int argc;
		char* token;
		char* argv[16];

		print_str(cur_dir);
		Sys_Uart_Putc('>');

		str_cmds[0] = 0;
		uart_gets(str_cmds, 63);

//		print_buf((int)str_cmds, str_cmds, 32);
//		sysprintf("cmds = %s\r\n", str_cmds);

		argc = 0;
		argv[0] = str_cmds;
		token = strtok(str_cmds, " ");
		while (token) {
			argv[argc++] = token;
//			sysprintf("token = %s\n", token);
//			uart_puts(token);
			token = strtok(0, " ");
		}

//		if (0 == argc) {
//			uart_puts("Nothing to do.");
//			continue;
//		}

		if (0 == strcmp(argv[0], "r")) {
			int addr;
			int data;

			if (argc < 2) {
				uart_puts("usage: r addr");
				continue;
			}

			addr = xtoi(argv[1]);
			data = *(volatile int*)addr;

			sysprintf("Read [%X] = %X\r\n", addr, data);
		} else if (0 == strcmp(argv[0], "exit")) {
			//break;
		} else if (0 == strcmp(argv[0], "dir")) {
			FRESULT fr;     /* Return value */
			DIR dj;         /* Directory object */
			FILINFO fno;    /* File information */
			const char* filter;

			if (argc > 1)
				filter = argv[1];
			else
				filter = "*";

			fr = f_findfirst(&dj, &fno, cur_dir, filter);
//			sysprintf("f_findfirst(%s, %s) return %d.\n", cur_dir, filter, fr);
			while (fr == FR_OK && fno.fname[0]) {
				sysprintf("%s %d byte(s)\r\n", fno.fname, (int)fno.fsize);
				fr = f_findnext(&dj, &fno);
//				sysprintf("f_findnext return %d.\n", fr);
			}

			f_closedir(&dj);
		} else if (0 == strcmp(argv[0], "cd")) {
			if (argc > 1) {
				f_chdir(argv[1]);
				f_getcwd(cur_dir, sizeof(cur_dir));
			}
		} else if (0 == strcmp(argv[0], "play")) {
//			if (argc > 1) play_pcm(argv[1]);
		} else if (0 == strcmp(argv[0], "d")) {
			int addr;
			int len;

			if (argc < 3) {
				uart_puts("usage: d addr len");
				continue;
			}

			addr = xtoi(argv[1]);
			len  = xtoi(argv[2]);

			print_buf(addr, (const char*)addr, len);
		} else if (0 == strcmp(argv[0], "rb")) {
			int addr;
			int data;

			if (argc < 2) {
				uart_puts("usage: rb addr");
				continue;
			}

			addr = xtoi(argv[1]);
			data = *(volatile unsigned char*)addr;

			sysprintf("Read Byte [%X] = %X\r\n", addr, data);
		} else if (0 == strcmp(argv[0], "rh")) {
			int addr;
			int data;

			if (argc < 2) {
				uart_puts("usage: rh addr");
				continue;
			}

			addr = xtoi(argv[1]);
			data = *(volatile unsigned short*)addr;

			sysprintf("Read Short [%X] = %X\r\n", addr, data);
		} else if (0 == strcmp(argv[0], "c")) {
			int ret;
			int addr;
			int arg[8];

			if (argc < 2) {
				uart_puts("usage: c addr [data0] [data1] ...");
				continue;
			}

			addr = xtoi(argv[1]);

			for (int i = 2; i < argc; i++)
				arg[i - 2] = xtoi(argv[i]);

			//ret = icall(addr, arg);
			ret = 0;

			sysprintf("Call %X = %X\r\n", addr, ret);
		} else if (0 == strcmp(argv[0], "w")) {
			int addr;
			int data;

			if (argc < 3) {
				uart_puts("usage: w addr data");
				continue;
			}

			addr = xtoi(argv[1]);
			data = xtoi(argv[2]);

			*(volatile int*)addr = data;

			sysprintf("Write [%X] = %X\r\n", addr, data);
		} else if (0 == strcmp(argv[0], "wb")) {
			int addr;
			int data;

			if (argc < 3) {
				uart_puts("usage: wb addr data");
				continue;
			}

			addr = xtoi(argv[1]);
			data = xtoi(argv[2]);

			data &= 0xFF;

			*(volatile unsigned char*)addr = data;

			sysprintf("Write Byte [%X] = %X\r\n", addr, data);
		} else if (0 == strcmp(argv[0], "wh")) {
			int addr;
			int data;

			if (argc < 3) {
				uart_puts("usage: wh addr data");
				continue;
			}

			addr = xtoi(argv[1]);
			data = xtoi(argv[2]);

			data &= 0xFFFF;

			*(volatile unsigned short*)addr = data;

			sysprintf("Write Short [%X] = %X\r\n", addr, data);
		} else if (0 == strcmp(argv[0], "load")) {
			int addr;

			if (argc < 2) {
				uart_puts("usage: load name [addr]");
				continue;
			}

			if (argc > 2)
				addr = xtoi(argv[2]);
			else
				addr = 0;

			sysprintf("Trying to load %s ...", argv[1]);

			//r = tfs_load((void*)addr, argv[1], SDRAM_SIZE);
			r = 0;
			if (r) {
				sysprintf("failed: %d.\r\n", r);
				continue;
			}

			sysprintf("OK\r\n");
		} else if (0 == strcmp(argv[0], "nes")) {
			if (argc > 1 && 0 == strcmp(argv[1], "reset")) {
				if (argc > 2) {
					int v = xtoi(argv[2]);
					// reset 1|0
				} else {
					// reset
				}
			}
		} else if (0 == strcmp(argv[0], "sd")) {
			if (argc > 1) {
				if (0 == strcmp(argv[1], "r")) {
					int sec;
					int addr;

					if (argc > 2)
						sec = xtoi(argv[2]);
					else
						sec = 0;

					if (argc > 3)
						addr = xtoi(argv[3]);
					else
						addr = 0x5000;

					//sdcard_read(sec, 1, (char*)addr);
				}
			}
		}

		uart_puts("OK.");
		vTaskDelay(50);
	}
}

static void NES_Task( void *pvParameters )
{
	int r;

	r = nes_load_for_fat("0:/bbk_bios10.nes");

	if (r == 2) {
		// bios not found
		r = nes_load_for_fat("0:/default.nes");
	}

	if (r == 2) {
		r = nes_load_for_flash(0x800000, 0xA010); // mario
	}

	for (;;)
		vTaskDelay(1000 / portTICK_PERIOD_MS);
}

static void load_sys_cfg_file(void)
{
	FIL f;
	int res;
	int valid;

	valid = 0;
	res = f_open(&f, ERGO_SYS_CFG_FILE, FA_READ);
	if (FR_OK == res) {
		UINT br;

		f_read(&f, &g_ergo_sys_cfg, sizeof(ergo_sys_cfg_t), &br);
		f_close(&f);

		// data check
		if (ERGO_SYS_CFG_MAGIC == g_ergo_sys_cfg.magic) {
			int i;
			int sum = 0;
			int* buf;

			buf = (int*)&g_ergo_sys_cfg + 2; // skip magic & sum fileds
			for (i = 0; i < sizeof(ergo_sys_cfg_t) / 4 - 2; i++) {
				sum += *buf++;
			}

			if (sum == g_ergo_sys_cfg.sum) {
				// data valid
				valid = 1;
				sysprintf("valid sys cfg file\r\n");
			}
		}
	}

	if (!valid) {
		// load default value
		memset(&g_ergo_sys_cfg, 0, sizeof(ergo_sys_cfg_t));

		g_ergo_sys_cfg.magic = ERGO_SYS_CFG_MAGIC;
		g_ergo_sys_cfg.flags[0] = 0xFFFFFFFF;
	}
}

int save_sys_cfg_file(void)
{
	FIL f;
	int res;

	int i;
	int sum = 0;
	int* buf;

	buf = (int*)&g_ergo_sys_cfg + 2; // skip magic & sum fileds
	for (i = 0; i < sizeof(ergo_sys_cfg_t) / 4 - 2; i++) {
		sum += *buf++;
	}

	g_ergo_sys_cfg.sum = sum;

	res = f_open(&f, ERGO_SYS_CFG_FILE, FA_CREATE_ALWAYS | FA_WRITE);
	if (FR_OK == res) {
		UINT bw;

		f_write(&f, &g_ergo_sys_cfg, sizeof(ergo_sys_cfg_t), &bw);
		f_close(&f);

		sysprintf("save sys cfg file: %d\r\n", bw);
	}

	return 0;
}

#ifdef HID_DEBUG_MODE
extern unsigned char hid_raw_data[2][256];
unsigned char hid_dup_data[256];
#endif

static void MAIN_Task(void* pvParameters)
{
	int need_refresh;
	(void)pvParameters;

	SD_INIT_CFG sd_cfg;

	ch9350_init();
	Sys_Uart_Init(UART1, get_cpu_frequency(), 115200, 1);

	sysprintf("RUN MAIN_Task\r\n");

	// wait display
	vTaskDelay(2000);

	if (the_app_env.card_ok) {
		sd_cfg.reset_io = 0;
		sd_cfg.type     = the_app_env.card_type;
		sd_cfg.uRCA     = the_app_env.card_uRCA;
	} else {
		sd_cfg.reset_io = 1;
		sd_cfg.type     = 0;
		sd_cfg.uRCA     = 0;
	}

	sysprintf("ENV: sd_type=%d sd_uRCA=%08X.\r\n", sd_cfg.type, sd_cfg.uRCA);

#ifndef HID_DEBUG_MODE
	for (;;) {
		if (0 == SD_Init(0, &sd_cfg)) {
			int r = f_mount(&the_fs, "0:", 1);
			if (r != FR_OK) {
				sysprintf("fatfs mount failed: %d.\r\n", r);
			} else {
				sysprintf("fatfs mount successfully.\r\n");
				break;
			}
		}

		sd_cfg.reset_io = 1;
		vTaskDelay(2000);
	}
#endif

//	event_gui_done = sys_event_create();
	event_gui_done = 0;

	is_emu_running = 0;

	/*GUI初始化*/
	lv_init();
	lv_port_disp_init();
	lv_port_indev_init();
//	lv_fs_init();

	// Load cfg data
	load_sys_cfg_file();

	desktop_start();

#ifndef HID_DEBUG_MODE
	xTaskCreate(NES_Task, "NES_Task", 1024, NULL, 3, NULL);
	xTaskCreate(CMD_Task, "CMD_Task", 1024, NULL, 3, NULL);

	// take a breath for nes task
	vTaskDelay(1000);
#endif

#ifdef HID_DEBUG_MODE0
	for (;;) {
		int i;
		int len;

		len = hid_raw_data[0][0];
		if (len & 0x80) {
			len &= 0x7F;

			memcpy(hid_dup_data, hid_raw_data[0], len);
			hid_raw_data[0][0] = 0;

			sysprintf("Port 0:");
			for (i = 1; i <= len; i++) {
				sysprintf(" %02X", hid_raw_data[0][i]);
			}
			sysprintf("\r\n");
		}

		len = hid_raw_data[1][0];
		if (len & 0x80) {
			len &= 0x7F;

			memcpy(hid_dup_data, hid_raw_data[1], len);
			hid_raw_data[1][0] = 0;

			sysprintf("Port 1:");
			for (i = 1; i <= len; i++) {
				sysprintf(" %02X", hid_raw_data[1][i]);
			}
			sysprintf("\r\n");
		}

		vTaskDelay(500);
	}
#endif

	desktop_refresh();
	lv_task_handler();

	need_refresh = 1;
	for (;;) {
		if (is_emu_running) {
			// do not run GUI
			need_refresh = 1;
			vTaskDelay(500);
			continue;
		} else if (need_refresh) {
			need_refresh = 0;
			desktop_refresh();
		}

#ifdef HID_DEBUG_MODE
		desktop_refresh();
		lv_task_handler();
		vTaskDelay(50);
#else
		lv_task_handler();
		vTaskDelay(1);
#endif
	}
}

int main(void)
{
	APP_ENV* env;

	Sys_Clock_Init(CPU_FREQUENCY);
	sys_mmu_init();

	Sys_Uart_Init(DEBUG_UART_PORT, get_cpu_frequency(), 115200, 0);
	Sys_SET_UART_DBG(DEBUG_UART_PORT);

	Sys_Timer1_Init();

	DMA_Init();

	sysprintf("\r\n\r\n\r\n");
	sysprintf("FreeRTOS Start!!!\r\n");
	sysprintf("[ " __DATE__ " "__TIME__ " ]...\r\n");
	sysprintf("Dram Init Size %d Mb.\r\n", 32);

	env = (APP_ENV*)ENV_OFFSET;
	sysprintf("start env=%08X\r\n", env);
	the_app_env = *env;

	sysSetLocalInterrupt(ENABLE_IRQ);

	xTaskCreate(MAIN_Task, "MAIN_Task", 1024, NULL, 3, NULL);

	vTaskStartScheduler();

	return 0;
}


